package com.example.demo18_custom_concurrentsession.config;

import com.example.demo18_custom_concurrentsession.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy;
import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserService userService;

    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }

    // 暴露 AuthenticationManager
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    SessionRegistry sessionRegistry(){
        return new SessionRegistryImpl();
    }

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        // 设置 AuthenticationManager
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        // 设置登录成功处理逻辑
        loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
        });
        // 设置登录失败处理逻辑
        loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("登录失败 " + exception.getMessage());
        });

        // 用于并发控制的 SessionAuthenticationStrategy，判断 sessionRegistry 中的哪些会话已经过期，然后标记已过期
        ConcurrentSessionControlAuthenticationStrategy concurrentStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        concurrentStrategy.setMaximumSessions(1);// 设置最大会话并发数
        // 用于登录成功后向 sessionRegistry 注册会话的 SessionAuthenticationStrategy
        RegisterSessionAuthenticationStrategy registerStrategy = new RegisterSessionAuthenticationStrategy(sessionRegistry());
        // 用于每次登录成功后改变 sessionId，防御固定会话攻击，提高系统安全性
        ChangeSessionIdAuthenticationStrategy changeStrategy = new ChangeSessionIdAuthenticationStrategy();
        // 包装前两个 Strategy
        CompositeSessionAuthenticationStrategy sessionStrategy =
                new CompositeSessionAuthenticationStrategy(Arrays.asList(concurrentStrategy,changeStrategy,registerStrategy));
        loginFilter.setSessionAuthenticationStrategy(sessionStrategy);
        return loginFilter;
    }

    // 配置 ConcurrentSessionFilter，用于拦截 sessionRegistry 中已经过期的会话
    @Bean
    ConcurrentSessionFilter concurrentSessionFilter(){
        // 第一个参数：sessionRegistry 维护并发会话 map
        // 第二个参数：拦截 sessionRegistry 中已经过期的会话的回调函数
        return new ConcurrentSessionFilter(sessionRegistry(), event -> {
            HttpServletResponse response = event.getResponse();
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("您已在另一台设备登录，本次登录已下线!");
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 自定义 LoginFilter 代替 UsernamePasswordAuthenticationFilter
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
        http.addFilterAt(concurrentSessionFilter(),ConcurrentSessionFilter.class);
        http.authorizeRequests()
                .anyRequest().authenticated()
//                .and().formLogin()
//                .successHandler((request, response, authentication) -> {
//                    response.setContentType("application/json;charset=UTF-8");
//                    response.getWriter().write(new ObjectMapper().writeValueAsString(authentication.getPrincipal()));
//                })
//                .failureHandler((request, response, exception) -> {
//                    response.setContentType("application/json;charset=UTF-8");
//                    response.getWriter().write("登录失败 " + exception.getMessage());
//                })
                .and()
                .csrf().disable()
                // 会话并发管理
//                .sessionManagement()
//                // 最大会话数为 1
//                .maximumSessions(1)
        ;
    }
}
